package com.jiangli.service.impl;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.Serializers;
import com.jiangli.context.BaseContext;
import com.jiangli.dto.CollectDishDTO;
import com.jiangli.dto.addCollectDishDTO;
import com.jiangli.entity.Dish;
import com.jiangli.entity.DishFlavor;
import com.jiangli.entity.UserFavorite;
import com.jiangli.exception.BaseException;
import com.jiangli.mapper.CollectMapper;
import com.jiangli.mapper.DishFlavorMapper;
import com.jiangli.mapper.DishMapper;
import com.jiangli.mapper.UserFavoriteMapper;
import com.jiangli.service.CollectService;
import com.jiangli.service.DishService;
import com.jiangli.service.UserService;
import com.jiangli.utils.RedisLock;
import com.jiangli.vo.CollectDishVO;
import com.jiangli.vo.DishVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.security.Key;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class CollectServiceImpl implements CollectService {

    @Autowired
    private CollectMapper collectMapper;
    @Autowired
    private ObjectMapper  objectMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private UserFavoriteMapper userFavoriteMapper;
    @Autowired
    private RedisLock redisLock;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DishService dishService;
    public static final String USER_FAVORITE_DISHIDLIST="user:favorite:dishIdList:";
    public static final String USER_FAVORITE_COUNT="user:favorite:count:";
    @Autowired
    private UserService userService;

    /**
     * 向用户添加收藏菜品
     * @param collectDishDTO
     */
    public void addDish(addCollectDishDTO collectDishDTO) {
        //检查
        Long dishId = collectDishDTO.getDishId();
        if(dishId == null) {
            throw new BaseException("传入参数为空");
        }
        //判断当前菜品用户表中是否存在
        //如果存在 抛出异常dhdd
        Long userId=BaseContext.getCurrentId();
        int dishCount = getUserFavoriteDishCount(userId);
        //获取用户菜品
        //遍历用户菜品 如果存在 直接报错说明存在该菜品 重复添加
        List<Long> userFavoriteDishIdList=getUserFavoriteDishIdList(userId,dishCount);
        userFavoriteDishIdList=new ArrayList<>(userFavoriteDishIdList);

            if (userFavoriteDishIdList!=null && userFavoriteDishIdList.size()>0) {
                for(Long userFavoriteDishId : userFavoriteDishIdList) {
                    if (dishId.equals(userFavoriteDishId)) {
                        throw new BaseException("该菜品已经存在");
                    }
                }
                //说明菜品中不存在该菜品
            }


        userFavoriteDishIdList.add(dishId);


        try {
            //将数据更新到redis中
            redisTemplate.opsForValue().set(USER_FAVORITE_DISHIDLIST+userId,
                    objectMapper.writeValueAsString(userFavoriteDishIdList),
                    30,TimeUnit.MINUTES);

            redisTemplate.opsForValue().set(
                    USER_FAVORITE_COUNT+userId, String.valueOf(userFavoriteDishIdList.size()),
                    30,TimeUnit.MINUTES);
        } catch (JsonProcessingException e) {
           log.error("将用户收藏菜品序列化失败");
        }

        try {
            UserFavorite userFavorite=new UserFavorite();
            userFavorite.setUserId(userId);
            userFavorite.setDishId(dishId);
            userFavoriteMapper.insert(userFavorite);
        } catch ( Exception e) {
        //数据库插入失败  删除redis中之前存入的信息 并且报错 显示插入失败
            redisTemplate.delete(USER_FAVORITE_DISHIDLIST+userId);
            redisTemplate.delete(USER_FAVORITE_COUNT+userId);
            log.error("插入用户数据失败{},{}",userId,e.getMessage());
            throw new BaseException("添加收藏失败，该菜品不存在");
        }
    }


    public List<Long> getUserFavoriteDishIdList(Long userId,int count){
        if(userId == null) {
            return null;
        }
        String key="user:favorite:dishIdList:"+userId;
        //1 从redis中尝试获取 用户的所以收藏菜品id
        String userFavoriteStr = redisTemplate.opsForValue().get(key);



        if(userFavoriteStr != null) {
            if (userFavoriteStr.equals("NULL")) {
                return null;
            }
            try {
                Long[] ids = objectMapper.readValue(userFavoriteStr, Long[].class);
                //如果与记录的总数一致 就返回队列
                if (ids.length == count) {
                    return List.of(ids);
                }
            } catch (Exception ex) {
             log.error("转换缓存失败");
             redisTemplate.delete(key);
            }
        }
                // 1 从redis中获取的数据为null
                // 2 redis中获取的长度和存储的总数不一致
               List<Long> dishIdListByDB=collectMapper.selectUserCollecDishIdtListById(userId);

                //说明用户就没收藏菜品
                if(dishIdListByDB==null){
                    //设置为null 之后从redis中获取就说明数据库为空 避免频繁查询null值
                    redisTemplate.opsForValue().set(key,"NULL",30,TimeUnit.SECONDS);
                    redisTemplate.opsForValue().set(USER_FAVORITE_COUNT+userId,"NULL",30,TimeUnit.SECONDS);
                    return dishIdListByDB;
                }
                //对于用户的收藏不为null的时候
        try {
            redisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(dishIdListByDB),30, TimeUnit.HOURS);
            count=  dishIdListByDB.size();
            //更新一下 用户收藏的总数
            redisTemplate.opsForValue().set(USER_FAVORITE_COUNT+userId, String.valueOf(count),30, TimeUnit.HOURS);
        } catch (JsonProcessingException e) {
            log.error("将用户的收藏菜品转为Long【】失败");
            //删除redis中错误的序列
            redisTemplate.delete(key);
        }

        return dishIdListByDB;


        //如果为null 那就直接返回数据库的信息

    }


    //用户获取收藏菜品
    public CollectDishVO showUserCollectDish() {
        Long userId=BaseContext.getCurrentId();
        String key=USER_FAVORITE_COUNT+userId;
        //获取用户的所有的收藏总数
        int count=getUserFavoriteDishCount(userId);
        List<Long> userFavoriteDishIdList = getUserFavoriteDishIdList(userId, count);
        //说明数据库中用户的收藏为null 就是没有收藏
      if (userFavoriteDishIdList == null || userFavoriteDishIdList.size() == 0) {
          return null;
      }
      //用户的菜品不为null 且存在 返回用户的收藏菜品
        List<Dish> dishesFromCacheOrDB = dishService.getDishesFromCacheOrDB(userFavoriteDishIdList);

      CollectDishVO collectDishVO = new CollectDishVO();
      collectDishVO.setCollectionDishSize((long) dishesFromCacheOrDB.size());
      collectDishVO.setCollectDishList(dishesFromCacheOrDB);

      return collectDishVO;
    }

    private int getUserFavoriteDishCount(Long userId) {
        String key=USER_FAVORITE_COUNT+userId;
        String s = redisTemplate.opsForValue().get(key);
        if(s!=null){
            int result=Integer.parseInt(s);
            return result;
        }
        //查询数据库 不用锁
       int result= userFavoriteMapper.selectUserFavoriteCount(userId);

        redisTemplate.opsForValue().set(key,String.valueOf(result),30,TimeUnit.MINUTES);
        return result;
    }

    public void delete(CollectDishDTO collectDishDTO) {

        if(collectDishDTO.getDishIds()==null||collectDishDTO.getDishIds().isEmpty()) {
            //不处理问题 处理的是
            log.error("dishIds为null" );
            throw  new BaseException("传入参数为null");
        }
        Long userId = BaseContext.getCurrentId();
        int dishCount = getUserFavoriteDishCount(userId);
        List<Long> userFavoriteDishIdList = getUserFavoriteDishIdList(userId, dishCount);
        List<Long> dishIds = collectDishDTO.getDishIds();
        List<Long> newDishIdList=new ArrayList<>(userFavoriteDishIdList);
        //遍历原来的数据 如果 不相同就 添加到新的队列中dish_flavor
        //将新数据 修改到redis中
        newDishIdList.removeAll(dishIds);
        if(newDishIdList.size()==userFavoriteDishIdList.size()){
            throw new BaseException("删除的菜品不在收藏里面");
        }


        try {
            redisTemplate.opsForValue().set(
                    USER_FAVORITE_DISHIDLIST+userId,
                    objectMapper.writeValueAsString(newDishIdList),
                    30,TimeUnit.MINUTES
            );
            redisTemplate.opsForValue().set(
                    USER_FAVORITE_COUNT+userId,
                    objectMapper.writeValueAsString(newDishIdList.size()),
                    30,TimeUnit.MINUTES
            );
        } catch (JsonProcessingException e) {
            redisTemplate.delete(USER_FAVORITE_DISHIDLIST+userId);
            redisTemplate.delete(USER_FAVORITE_COUNT+userId);
            log.error("将新收藏菜品序列化失败");

        }
        //将数据库中的老数据删除
        try {
            userFavoriteMapper.deleteByUidAndDishId(userId,dishIds);
        } catch (Exception e) {
            redisTemplate.delete(USER_FAVORITE_DISHIDLIST+userId);
            redisTemplate.delete(USER_FAVORITE_COUNT+userId);
            log.error("删除用户收藏失败");
            throw new BaseException("删除用户搜藏失败");
        }
    }

    @Override
    public boolean exit(Long userId, Long dishId) {
        int count = getUserFavoriteDishCount(userId);
        List<Long> userFavoriteDishIdList = getUserFavoriteDishIdList(userId, count);
        if(userFavoriteDishIdList == null || userFavoriteDishIdList.size() == 0) {
            return false;
        }
        for (Long userFavoriteDishId : userFavoriteDishIdList) {
            if(userFavoriteDishId.equals(dishId)) {
                return true;
            }
        }
        return false;
    }
}
